package com.ideaaedi.shardingdynamictable.config.sharding;

import com.google.common.collect.BoundType;
import com.google.common.collect.Lists;
import com.google.common.collect.Range;
import com.ideaaedi.shardingdynamictable.config.sharding.support.ShardingTableSupport;
import com.ideaaedi.shardingdynamictable.entity.bo.DatasourceArgs;
import com.ideaaedi.shardingdynamictable.util.DatabaseUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.shardingsphere.sharding.api.sharding.standard.PreciseShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.RangeShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.StandardShardingAlgorithm;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 日期(yyyy-MM-dd)分片算法
 * <br />
 * 此类已通过sharding的SPI加入spring容器(见{@link DateShardingAlgorithm#getType()}说明)，可直接使用Resource等进行依赖注入
 *
 * @author JustryDeng
 * @since 2022/10/6 23:40:28
 */
@Slf4j
public class DateShardingAlgorithm implements StandardShardingAlgorithm<String> {
    
    @Resource
    ShardingTableSupport shardingTableSupport;
    
    @Resource
    DatasourceArgs datasourceArgs;
    
    /**
     * shardingValue.getValue()值只有一个，那么返回对应的实际表名
     * @param availableTargetNames
     *            可用的目标节点表(即：当前数据源下，actual-data-nodes 对应的表)
     */
    @Override
    public String doSharding(Collection<String> availableTargetNames,
                             PreciseShardingValue<String> shardingValue) {
        // 逻辑表名， 如：trans_record
        final String logicTableName = shardingValue.getLogicTableName();
        // 分片列的当前值， 如：2022-10-06
        final String value = shardingValue.getValue();
        // 组装获得实际表名， 如：trans_record_202210
        final String[] arr = value.split("-");
        final String realTableName = logicTableName + "_" + arr[0] + arr[1];
        // 确保realTableName表存在，无则创建
        ensureTableExist(logicTableName, realTableName);
        if (!availableTargetNames.contains(realTableName)) {
            availableTargetNames.add(realTableName);
        }
        return realTableName;
    }
    
    /**
     * shardingValue.getValueRange()值对应一个范围，那么返回对应范围的多个实际表名
     * <br />
     * 如; 查询时指定 trans_date <= 2022-10-07 and trans_date >= 2022-08-07,
     *     那么 返回的表名集合就应该为 trans_record_202208 ... trans_record_202210
     *
     * @param availableTargetNames
     *            可用的目标节点表(即：当前数据源下，actual-data-nodes对应的表)
     */
    @Override
    @SuppressWarnings("AlibabaLowerCamelCaseVariableNaming")
    public Collection<String> doSharding(Collection<String> availableTargetNames,
                                         RangeShardingValue<String> shardingValue) {
        List<String> targetTableList = new ArrayList<>(availableTargetNames);
        
        final String logicTableName = shardingValue.getLogicTableName();
        final Range<String> valueRange = shardingValue.getValueRange();
        
        /// 上界值
        StringBuilder upperTableInfo = new StringBuilder();
        if (valueRange.hasUpperBound()) {
            final String upperEndpoint = valueRange.upperEndpoint();
            final String[] arr = upperEndpoint.split("-");
            String yyyyMM = arr[0] + arr[1];
            String upperTableName = logicTableName + "_" + yyyyMM;
            final BoundType upperBoundType = valueRange.upperBoundType();
            if (BoundType.CLOSED == upperBoundType) {
                upperTableInfo.append(" <= ").append(upperTableName);
            } else if (BoundType.OPEN == upperBoundType) {
                upperTableInfo.append(" < ").append(upperTableName);
            }
            targetTableList = targetTableList.stream().filter(realTableName -> {
                // BoundType.CLOSED - 闭区间， 上界包含当前upperEndpoint值 ， 即: <= upperEndpoint
                // BoundType.OPEN - 开区间， 上界不包含当前upperEndpoint值 ， 即: < upperEndpoint
                if (BoundType.CLOSED == upperBoundType) {
                    return realTableName.compareTo(upperTableName) <= 0;
                } else if (BoundType.OPEN == upperBoundType) {
                    return realTableName.compareTo(upperTableName) < 0;
                } else {
                    throw new RuntimeException("upperBoundType should not be null.");
                }
            }).collect(Collectors.toList());
        }
        
        /// 下界值
        StringBuilder lowerTableInfo = new StringBuilder();
        if (valueRange.hasLowerBound()) {
            final String lowerEndpoint = valueRange.lowerEndpoint();
            final String[] arr = lowerEndpoint.split("-");
            String yyyyMM = arr[0] + arr[1];
            String lowerTableName = logicTableName + "_" + yyyyMM;
            final BoundType lowerBoundType = valueRange.lowerBoundType();
            if (BoundType.CLOSED == lowerBoundType) {
                lowerTableInfo.append(" >= ").append(lowerTableName);
            } else if (BoundType.OPEN == lowerBoundType) {
                lowerTableInfo.append(" > ").append(lowerTableName);
            }
            targetTableList = targetTableList.stream().filter(realTableName -> {
                // BoundType.CLOSED - 闭区间， 下界包含当前upperEndpoint值 ， 即: >= upperEndpoint
                // BoundType.OPEN - 开区间， 下界不包含当前upperEndpoint值 ， 即: > upperEndpoint
                if (BoundType.CLOSED == lowerBoundType) {
                    return realTableName.compareTo(lowerTableName) >= 0;
                } else if (BoundType.OPEN == lowerBoundType) {
                    return realTableName.compareTo(lowerTableName) > 0;
                } else {
                    throw new RuntimeException("lowerBoundType should not be null.");
                }
            }).collect(Collectors.toList());
        }
        
        if (targetTableList.size() == 0) {
            log.info("availableTargetNames -> {}", availableTargetNames);
            log.error("Cannot determine realTableName whose logicTableName is '{}'."
                                                 + (upperTableInfo.length() == 0 ? "" : "\tsatisfy: realTableName" + upperTableInfo)
                                                 + (lowerTableInfo.length() == 0 ? "" : "\tsatisfy: realTableName" + lowerTableInfo)
                    ,  logicTableName);
            return Collections.emptyList();
        }
        // 确保realTableName表存在
        for (String realTableName : targetTableList) {
            ensureTableExist(logicTableName, realTableName);
        }
        return targetTableList;
    }
    
    @Override
    public void init() {
    
    }
    
    /**
     * spi注册
     */
    @Override
    public String getType() {
        // 同时，需要在resource下的/META-INF/services/下创建org.apache.shardingsphere.sharding.api.sharding.standard.StandardShardingAlgorithm文件，并指定当前类为其SPI实现
        return "JD_SHARDING";
    }
    
    /**
     * 确保真实表存在，没有则创建
     *
     * @param logicTableName
     *            逻辑表名
     * @param realTableName
     *            真实表名
     */
    private void ensureTableExist(String logicTableName, String realTableName) {
        Set<String> realTableNameSet =
                shardingTableSupport.getRealTableName(datasourceArgs, logicTableName);
        if (!realTableNameSet.contains(realTableName)) {
            // 批量执行创建真实表
            DatabaseUtil.executeSql(datasourceArgs,
                    Lists.newArrayList(
                            // 生成所有生成真实表的DDL
                            shardingTableSupport.obtainRealTableDdl(
                                    datasourceArgs, logicTableName, realTableName)));

            shardingTableSupport.addRealTableName(datasourceArgs, logicTableName, realTableName);
        }
    }
}
